package com.fanx.redis.lock.core;

import io.lettuce.core.RedisClient;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Redis 锁实现
 *
 * @author fanhang
 */
public class RedisLockImpl implements RedisLockable {

	private final Logger log = LoggerFactory.getLogger(RedisLockImpl.class);

	private static final String LOCK_PREFIX = "REDIS-LOCK:";

	private final String StatusOK = "OK";

	/**
	 * 确保原子性, LUA脚本释放锁：如果value的值与参数相等,则删除,否则返回0
	 */
	private static final String UNLOCK_SCRIPT_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

	private RedisClient redisClient;

	public RedisLockImpl(RedisClient redisClient) {
		this.redisClient = redisClient;
	}

	/**
	 * 添加锁, 按指定参数进行阻塞重试, 直到加锁成功
	 * @param lockName 在 redis 中体现为 key: [LOCK_PREFIX + lockName]
	 * @param value 存放到 key 之中, 用于【释放时验证】, 以防释放他人的锁
	 * @param expireSeconds 超时秒数, 以防应用崩溃情况下的锁无法释放
	 * @param retryCount 重试获取锁的次数
	 * @param retryInterval 重试间隔
	 * @return
	 * @throws InterruptedException
	 * @see {@link #tryLock(String, String, int)}
	 */
	@Override
	public boolean lock(String lockName, String value, int expireSeconds, int retryCount, int retryInterval)
			throws InterruptedException {
		boolean result = false;
		for (int i = 0; i < retryCount; i++) {
			result = tryLock(lockName, value, expireSeconds);
			if (result) {
				log.debug("redis-lock [{}, {}] success", lockName, value);
				break;
			}
			log.debug("redis-lock [{}, {}] failed, wait retry...", lockName, value);
			Thread.sleep(retryInterval);
		}
		return result;
	}

	/**
	 * 尝试添加锁, 无论成功失败, 立即返回结果
	 * @param lockName 在 redis 中体现为 key: [LOCK_PREFIX + lockName]
	 * @param value 存放到 key 之中, 用于【释放时验证】, 以防释放他人的锁
	 * @param expireSeconds 超时秒数, 以防应用崩溃情况下的锁无法释放
	 * @return
	 */
	@Override
	public boolean tryLock(String lockName, String value, int expireSeconds) {
		StatefulRedisConnection<String, String> connection = redisClient.connect();
		RedisCommands<String, String> commands = connection.sync();
		String result = commands.set(LOCK_PREFIX + lockName, value, SetArgs.Builder.nx().ex(expireSeconds));
		log.debug("redis-lock try: [{}, {}, {}], result: {}", lockName, value, expireSeconds, result);
		return StatusOK.equals(result);
	}

	/**
	 * 释放锁
	 * @param lockName 在 redis 中体现为 key: [LOCK_PREFIX + lockName]
	 * @param value 按此 value 进行验证, 以防释放他人的锁
	 * @return
	 */
	@Override
	public boolean unlock(String lockName, String value) {
		StatefulRedisConnection<String, String> connection = redisClient.connect();
		RedisCommands<String, String> commands = connection.sync();
		Long result = commands.eval(UNLOCK_SCRIPT_LUA, ScriptOutputType.INTEGER,
				new String[] { LOCK_PREFIX + lockName }, value);
		log.debug("redis-unlock: [{}, {}], result: {}", lockName, value, result);
		return result != null && result.intValue() == 1;
	}

}
